define(['knockout', 'util','model','config', 'knockout-mapping'], function (ko, Util,Model,Config) {
    function Store(params) {
        //the params contains the config
        $.extend(this, {
            autoLoad: true,
            pageSize: 10,
            pageNo: 1,
            cache: false,//cache the store for mobile
            proxy: {
                type: Config.store.proxyType,
                url: '',
                originalUrl: ''
            },
            //use the _model in the callback
            onInitComplete: undefined,
            query: {},//the form query
            fixedQuery: {},
            model: 'model',// the model can be a string or require model
            initWith: undefined,
            rawData: false,//the records will store the raw ajax data
            formatter:undefined,//the function for format the result from server
            pageNoName:Config.store.pageNoName,
            pageSizeName:Config.store.pageSizeName,
            method:Config.store.method
        });

        var tempProxy = params.proxy;

        delete params.proxy;
        Util.extend(this, params);
        $.extend(this.proxy,tempProxy);

        if (this.id && this.id == 'random') this.id = '{0}Store_{1}'.formatTpl(this.model, Util.uuid(8, 16));

        var scope = this;

        scope._loadListeners = {
            'complete': []
        };
        scope.totalCount = ko.observable();
        scope.records = ko.observableArray();
        scope.pageCount = 0;

        if (scope.id) {
            ko.root.storeCache[scope.id] = scope;
        }

        if (scope.model) {
            //define a function for model init
            function initModel(model) {
                scope._model = model;
                //create the model for get the proxy skip the field init logic
                scope._modelInstance = new model({skipInit: true});

                if (!scope.proxy.url) {
                    scope.setProxy(scope._modelInstance.getProxy())
                }

                scope.proxy.originalUrl = scope.proxy.url;

                if (scope.initWith || scope.autoLoad) {
                    scope.load();
                }
                //we need require the model class when the model is string
                //we need a callback to parent vm because the require is async
                if (scope.onInitComplete) {
                    scope.onInitComplete();
                }
            }

            //a string model need required
            if (typeof scope.model == 'string') {
                //change the model to a object for add other property dynamically
                scope.model = {name: scope.model};

                if(scope.model.name == 'model'){
                    initModel(Model)
                }
                else {
                    //dynamic load model class and invoke the finish callback if necessary
                    require([scope.model.name], function (model) {
                        initModel(model)
                    });
                }
            } else {
                //the type of model is function
                initModel(scope.model);
            }
        }
    }

    Store.prototype.first = function () {
        if (this.pageNo != 1) {
            this.pageNo = 1;
            this.load();

            return true;
        }

        return false;
    };

    Store.prototype.next = function () {
        if (this.pageNo < this.pageCount) {
            ++this.pageNo;
            this.load();

            return true;
        }

        return false;
    };

    Store.prototype.pre = function () {
        if (this.pageNo > 1) {
            --this.pageNo;
            this.load();

            return true;
        }

        return false;
    };

    Store.prototype.last = function () {
        if (this.pageNo != this.pageCount) {
            this.pageNo = this.pageCount;
            this.load();

            return true;
        }

        return false;
    };

    Store.prototype.createModel = function (data) {
        var scope = this;

        function _createModel(model) {
            var tempModel = new model({isKo: true, default: scope.model.default});

            tempModel.setProxy(scope.getProxy());
            if (scope.id) {
                tempModel._storeId = scope.id;
            }

            if(data&&data.callback){
                data.callback(tempModel);
            }
            else {
                return tempModel;
            }
        }

        if(scope._model) {
            return _createModel(scope._model);
        }
        else {
            require([scope.model.name], function (model) {
                _createModel(model)
            });
        }
    };
    //callback invoked after load finish
    //firstPage is true will go to page 1
    Store.prototype.load = function (params) {
        if (!params) params = {};

        var scope = this;

        if(scope.initWith) {
            if (!scope.cache) scope.records.removeAll();
            scope.initRecords(scope.initWith);
            if(scope.onLoadComplete) scope.onLoadComplete();
            return;
        }

        var callback = params.callback;
        var firstPage = params.firstPage ? true : false;

        if (scope.proxy.url) {
            var url = Util.generateUrl(scope.proxy);
            var queryParams = {};

            $.extend(queryParams, scope.fixedQuery);
            $.extend(queryParams, scope.query);

            if (firstPage) {
                scope.totalCount(0);
                scope.pageNo = 1;
            }

            if (scope.pageNo > 0 && scope.pageSize > 0) {
                queryParams[scope.pageNoName] = scope.pageNo;
                queryParams[scope.pageSizeName] = scope.pageSize;
            }

            Util['ajax{0}'.formatTpl(scope.method)]({
                url: url, data: queryParams, callback: function (data) {
                    if(Config.store.formatter) data = Config.store.formatter(data);
                    if(scope.formatter) data = scope.formatter(data);

                    if (data.success) {
                        if (!scope.cache) scope.records.removeAll();
                        scope.totalCount(data.totalCount);

                        if (scope.pageSize > 0) {
                            scope.pageCount = Math.ceil(scope.totalCount() / scope.pageSize);
                        }

                        if (data.data && data.data instanceof Array) {
                            if (scope.rawData) {
                                scope.records(data.data);
                            } else {
                                var models = [];

                                for (var rIndex = 0; rIndex < data.data.length; ++rIndex) {
                                    var model = scope.createModelFormOBJ(data.data[rIndex]);

                                    if (scope.pageNo > 0 && scope.pageSize > 0) {
                                        model.rowId = ko.observable((scope.pageNo - 1) * scope.pageSize + rIndex + 1);
                                    } else {
                                        model.rowId = ko.observable(rIndex + 1);
                                    }

                                    model.rowCheck = ko.observable(false);
                                    models.push(model);
                                }

                                scope.records(scope.records().concat(models));
                            }

                        }

                        if (callback) callback();
                        if (scope.onLoadComplete) scope.onLoadComplete();

                        scope._loadListeners['complete'].forEach(function (listener) {
                            listener();
                        });
                    }
                }
            });
        }
    };

    Store.prototype.initRecords = function (data) {
        var scope = this;
        var models = [];

        data.forEach(function (record) {
            switch (typeof record) {
                case 'object':
                    models.push(scope.createModelFormOBJ(record));
                    break;
                case 'string':
                    models.push(scope.createModelFormOBJ({title:record}));
                    break;
            }
        });

        scope.records(scope.records().concat(models));
    };

    Store.prototype.createModelFormOBJ = function (obj) {
        var scope = this;
        var parentModel = new scope._model();

        parentModel.fields.forEach(function (field) {
            //skip the id and rowId because the field's type is undefined
            if (field.type) {
                switch (field.type) {
                    case 'file':
                        obj[field.name] = field.defaultValue;
                        break;
                    default:
                        if (obj[field.name] == undefined) {
                            obj[field.name] = field.defaultValue;
                        }
                }
            }
        });

        var record = ko.mapping.fromJS(obj);
        var model = $.extend(parentModel, record);

        model._original = JSON.stringify(obj);//for model revert
        model._cssActive = ko.observable(false);//add the ko field for ui active
        model._icon = {};//add icon css field for ui bind
        model._operations = [];//add record level operation，
        model._href = '#' + (obj.href ? obj.href : '');//for router
        model._query = scope.query;//use when model ajax request

        if (obj.icon) {
            model._icon = Util.zico(obj.icon);
        }

        if (obj.operations && obj.operations.length > 0) {
            model._operations = Util.generateOperations(obj.operations);
        }

        if (scope.id) {
            model._storeId = scope.id;
        }

        model.setProxy(scope.getProxy());

        return model;
    };

    Store.prototype.empty = function () {
        this.records.removeAll();
        this.totalCount(0);
        this.pageNo = 1;
    };

    Store.prototype.setProxy = function (proxy) {
        $.extend(this.proxy, proxy);
    };

    Store.prototype.setProxyFromTpl = function () {
        this.proxy.url = this.proxy.originalUrl.formatTpl(arguments);
    };

    Store.prototype.getProxy = function () {
        return this.proxy;
    };

    Store.prototype.addLoadListener = function (key, callback) {
        if (this._loadListeners[key]) {
            this._loadListeners[key].push(callback);
        }
    };
    //find a record with id from the store cache
    Store.prototype.getRecordById = function (id) {
        var record = undefined;
        var records = this.records();

        if (records && records.length > 0) {
            for (rIndex = 0; rIndex < records.length; ++rIndex) {
                if (records[rIndex].id() == id) {
                    record = records[rIndex];

                    break;
                }
            }
        }

        return record;
    };
    //the fields not contain additional binding attr
    Store.prototype.getModelFields = function () {
        if (this._modelInstance) {
            return this._modelInstance.fields;
        }
    };
    //the field not contain additional binding attr
    Store.prototype.getModelField = function (name) {
        if (this._modelInstance) {
            return this._modelInstance.getField(name);
        }
    };

    Store.prototype.getId = function () {
        return this.id;
    };

    return Store;
});
